#include <stdio.h>
#include <unistd.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include<malloc.h>

typedef struct S_Attr{
		int fd;
		int conected;
                int MaxCon;
		struct sockaddr_in infoConection;
}SocketAttr;

typedef struct Sock_S{
		struct S_Attr* Attr;
		void (*Open) (struct Sock_S*);
                void (*closeSocket) (struct Sock_S);
		char* (*Get_dir) (struct Sock_S);
                int (*Get_MaxCon) (struct Sock_S* sock);
		int (*Get_family) (struct Sock_S);
		int (*Get_port) (struct Sock_S);
		int (*Get_fd) (struct Sock_S);
                struct sockaddr_in (*Get_InfoConection) (struct Sock_S sock);
                void (*Set_MaxCon) (struct Sock_S* sock,int Max);
		void (*Set_dir) (struct Sock_S*,char*);
		void (*Set_family) (struct Sock_S*,int);
		void (*Set_port) (struct Sock_S*,int);
		void (*Set_fd) (struct Sock_S*,int);
		void (*Set_sinZero) (struct Sock_S*);
		void (*bindear) (struct Sock_S* sock);
                void (*EscuchaPorConecciones) (struct Sock_S* sock);
                int (*AceptaConecciones) (struct Sock_S*, struct Sock_S*);
                void (*reutilizar_puerto) (struct Sock_S* socket);
                void (*conectate) (struct Sock_S*,struct Sock_S*);
}Socket_T;

/*Setters y Getters*/
void Set_dir(Socket_T* sock,char* dir){
  sock->Attr->infoConection.sin_addr.s_addr=inet_addr(dir);
}

char* Get_dir(Socket_T sock){
  char* dir=(char*)malloc(sizeof(char)*20);
  strcpy(dir,inet_ntoa(sock.Attr->infoConection.sin_addr));
return(dir);
}

void Set_port(Socket_T* sock,int p){
    sock->Attr->infoConection.sin_port=p;
}

int Get_port(Socket_T sock){
    return(sock.Attr->infoConection.sin_port);
}

void Set_family(Socket_T* sock,int fam){
    sock->Attr->infoConection.sin_family=fam;
}

int Get_family(Socket_T sock){
    return(sock.Attr->infoConection.sin_family);
}

void Set_fd(Socket_T* sock,int fd){
    (sock->Attr)->fd=fd;
}

int Get_fd(Socket_T sock){
    return(sock.Attr->fd);
}

void Set_sinZero(Socket_T* sock){
    memset(sock->Attr->infoConection.sin_zero,'\0', 8);
}

struct sockaddr_in Get_InfoConection(Socket_T sock){
  return(sock.Attr->infoConection);
}
/*End Setters y Getters*/

/*Operaciones Sobre Sockets*/
void OpenSockStream (Socket_T* sock){
    sock->Set_fd(sock,socket(AF_INET,SOCK_STREAM,0));
    if((sock->Get_fd(*sock))==-1)
	  perror("socket");

    sock->Set_sinZero(sock);
}

void closeSocket(Socket_T sock){
    close(sock.Attr->fd);
}

void Set_MaxCon(Socket_T* sock,int Max){
  sock->Attr->MaxCon=Max;
}

int Get_MaxCon(Socket_T* sock){
  return(sock->Attr->MaxCon);
}

void EscuchaPorConecciones(Socket_T* sock){
  if(listen(sock->Get_fd(*sock),sock->Get_MaxCon(sock))==-1)
    perror("listen");
}

int AceptaConecciones(struct Sock_S* sock,Socket_T* AcceptedConection){

  AcceptedConection->Set_fd(AcceptedConection,accept(sock->Get_fd(*sock),NULL,NULL));

  if(AcceptedConection->Get_fd(*AcceptedConection)==-1){
      perror("accept");
      return -1;
    }

  return 0;
}

void reutilizar_puerto(struct Sock_S* socket){
    int yes=1;
    if (setsockopt(socket->Get_fd(*socket),SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
        perror("setsockopt");
    }
}

void bindear(Socket_T* sock){
  struct sockaddr_in info;

  info=sock->Get_InfoConection(*sock);

      if(bind(sock->Get_fd(*sock),(struct sockaddr*) &info,sizeof(struct sockaddr)) == -1){
        sock->closeSocket(*sock);
        perror ("bind");
        return;
      }

}

void conectate (struct Sock_S *sockCliente,struct Sock_S *sockServer){
  struct sockaddr_in serv_dire=sockServer->Get_InfoConection(*sockServer);

  if(connect(sockCliente->Get_fd(*sockCliente),(struct sockaddr*) &serv_dire,sizeof(struct sockaddr))==-1){
     perror("connect");
    }
}


Socket_T* NewSocketStream(void){

Socket_T* socket;

  socket=(Socket_T*)malloc(sizeof(Socket_T));
  socket->Attr=(SocketAttr*)malloc(sizeof(SocketAttr));

/*Enlazo con los Set_metodos*/
  socket->Set_MaxCon=Set_MaxCon;
  socket->Set_dir=Set_dir;
  socket->Set_family=Set_family;
  socket->Set_fd=Set_fd;
  socket->Set_port=Set_port;
/*Enlazo con los Get_metodos*/
  socket->Get_MaxCon=Get_MaxCon;
  socket->Get_InfoConection=Get_InfoConection;
  socket->Get_dir=Get_dir;
  socket->Get_family=Get_family;
  socket->Get_fd=Get_fd;
  socket->Get_port=Get_port;
/*Enlazo con los metodos de operaciones*/
  socket->Open=OpenSockStream;
  socket->closeSocket=closeSocket;
  socket->EscuchaPorConecciones=EscuchaPorConecciones;
  socket->AceptaConecciones=AceptaConecciones;
  socket->bindear=bindear;
  socket->Set_sinZero=Set_sinZero;
  socket->reutilizar_puerto=reutilizar_puerto;
  socket->conectate=conectate;
  return socket;
}



